/*
 * Copyright (c) 2009. Silenus Consultoria, S.L.
 */
package es.silenus.detecta.util;

import net.sf.jasperreports.engine.JRExporter;
import net.sf.jasperreports.engine.util.FileResolver;
import net.sf.jasperreports.engine.util.JRProperties;
import net.sf.jasperreports.engine.util.SimpleFileResolver;

import org.apache.commons.lang.SystemUtils;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.beans.factory.InitializingBean;

import org.springframework.core.io.Resource;

import java.io.IOException;

import java.util.Map;
import java.util.Properties;


/**
 * Jasper reports helper.
 *
 * @author <a href="mailto:malonso@silenus-consultoria.es">Mariano Alonso</a>
 *
 * @since 21-may-2009 13:49:49
 */
public class JasperReportsHelper implements InitializingBean {
	/**
	 * Logger for this class.
	 */
	private static final Log LOG = LogFactory.getLog(JasperReportsHelper.class);

	/**
	 * Jasper Report compilation classpath.
	 */
	public static final String JASPER_COMPILE_PATH = "jasper.reports.compile.class.path";

	/**
	 * The jasper reports configuration as properties.
	 */
	private Properties properties;

	/**
	 * List of jars and directories to add to classpath for jasper compilation.
	 */
	private Resource[] classpath;

	/**
	 * The exporters.
	 */
	private Map<String, Class<JRExporter>> exporters;

	/**
	 * The exporters.
	 */
	private Map<String, Map<String, Object>> exporterParameters;

	/**
	 * The font directory.
	 */
	private Resource fontDirectory;

/**
   * Constructor.
   */
	public JasperReportsHelper() {
	}

	/**
	 * Sets the properties.
	 *
	 * @param properties the properties.
	 */
	public void setProperties(final Properties properties) {
		this.properties = properties;
	}

	/**
	 * Sets the classpath.
	 *
	 * @param classpath the classpath.
	 */
	public void setClasspath(final Resource[] classpath) {
		this.classpath = classpath;
	}

	/**
	 * Sets the exporters.
	 *
	 * @param exporters the exporters.
	 */
	public void setExporters(final Map<String, Class<JRExporter>> exporters) {
		this.exporters = exporters;
	}

	/**
	 * Sets the exporter parameters.
	 *
	 * @param exporterParameters the exporter parameters.
	 */
	public void setExporterParameters(final Map<String, Map<String, Object>> exporterParameters) {
		this.exporterParameters = exporterParameters;
	}

	/**
	 * Sets the font directory.
	 *
	 * @param fontDirectory the font directory.
	 */
	public void setFontDirectory(final Resource fontDirectory) {
		this.fontDirectory = fontDirectory;
	}

	/**
	 * Builds a path.
	 *
	 * @return the path.
	 */
	private String buildPath() {
		// Add to path contents of /WEB-INF/lib and also /WEB-INF/classes
		final String pathSeparator = SystemUtils.PATH_SEPARATOR;
		final StringBuilder path = new StringBuilder();

		try {
			if(classpath != null) {
				for(Resource resource : classpath) {
					path.append(resource.getFile().getAbsolutePath());
					path.append(pathSeparator);
				}
			}

			path.append(fontDirectory.getFile().getAbsolutePath());
		} catch(IOException e) {
			LOG.warn("Classpath resource not found", e);
		}

		return path.toString();
	}

	/**
	 * Invoked by a BeanFactory after it has set all bean properties supplied (and satisfied BeanFactoryAware and
	 * ApplicationContextAware).<p>This method allows the bean instance to perform initialization only possible
	 * when all bean properties have been set and to throw an exception in the event of misconfiguration.</p>
	 *
	 * @throws Exception in the event of misconfiguration (such as failure to set an essential property) or if
	 * 				 initialization fails.
	 */
	public void afterPropertiesSet() throws Exception {
		// Turn on jasper report properties
		if(properties == null) {
			properties = new Properties();
		}

		// Add classpath
		properties.put(JASPER_COMPILE_PATH, buildPath());
		properties.put(JRProperties.PDF_FONT_DIRS_PREFIX, fontDirectory.getFile().getAbsolutePath());
		properties.putAll(System.getProperties());
		System.setProperties(properties);

		//  Prepare exporter parameters
		if(exporterParameters != null) {
			for(Map.Entry<String, Map<String, Object>> exporterParameter : exporterParameters.entrySet()) {
				for(Map.Entry<String, Object> parameter : exporterParameter.getValue().entrySet()) {
					parameter.setValue(convertExporterParameter(parameter.getValue()));
				}
			}
		}
	}

	/**
	 * Converts a exporter parameter.
	 *
	 * @param value the value.
	 *
	 * @return the converted element.
	 */
	public Object convertExporterParameter(final Object value) {
		if(value instanceof String) {
			String str = (String)value;

			if("true".equals(str)) {
				return Boolean.TRUE;
			} else if("false".equals(str)) {
				return Boolean.FALSE;
			} else if((str.length() > 0) && Character.isDigit(str.charAt(0))) {
				// Looks like a number... let's try.
				try {
					return new Integer(str);
				} catch(NumberFormatException ex) {
					// OK, then let's keep it as a String value.
					return str;
				}
			}
		}

		return value;
	}

	/**
	 * Retrieves the file resolver.
	 *
	 * @return the file resolver.
	 */
	public FileResolver getFileResolver() {
		try {
			return new SimpleFileResolver(fontDirectory.getFile());
		} catch(IOException e) {
			LOG.error("Error retrieving file resolver", e);
		}

		return null;
	}

	/**
	 * Retrieves a exporter.
	 *
	 * @param format the format.
	 *
	 * @return the exporter.
	 */
	public JRExporter getExporter(final String format) {
		try {
			JRExporter exporter = exporters.get(format).newInstance();

			exporter.setParameters(exporterParameters.get(format));

			return exporter;
		} catch(Throwable e) {
			LOG.error("Error building exporter", e);
		}

		return null;
	}
}
